package epsofts.Neutron;

import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.List;
import java.util.Vector;

/**
 * Contient les objets du jeu, g�re le d�roulement du jeu.
 * 
 * @version 1.0
 * @created 14-sept.-2006 22:48:06
 */
public class Jeu
{
	private final String	ERREUR_JEU_DEMARRE	= "Erreur: Jeu d�j� d�marr�";

	/** Classe d'exception en cas de match null */
	public static class DrawGameException extends Exception
	{
		/**
		 * 
		 */
		private static final long	serialVersionUID	= 1L;

		/**
		 * Constructeur de la classe d'exception DrawGameException
		 */
		public DrawGameException(String sMessage)
		{
			super(sMessage);
		}
	};

	/**
	 * Joueur bleu.
	 */
	private IJoueur			m_joueurBleu	= null;

	/**
	 * Plateau courant.
	 */
	private Plateau			m_plateau		= new Plateau();

	/**
	 * Joueur rouge.
	 */
	private IJoueur			m_joueurRouge	= null;

	/**
	 * Vainqueur.
	 */
	private eCase			m_Vainqueur		= eCase.VIDE;

	/**
	 * Historique des plateaux jou�s.
	 */
	private List<Plateau>	m_Historique = new Vector<Plateau>();
	private int				m_CurseurHistorique = 0;

	/**
	 * Flux de sortie utilis�.
	 */
	private PrintStream		m_sortie		= null;

	/**
	 * Thread de traitement.
	 */
	private Thread			m_Thread;

	/**
	 * Lien vers l'Interface Utilisateur (console, GUI, ...)
	 */
	private INeutronUI		m_UI			= null;

	/**
	 * Cr�� un jeu avec les joueurs donn�s.
	 * 
	 * @param i_NeutronUI
	 *            Interface Utilisateur.
	 * @param joueurRouge
	 *            Joueur rouge.
	 * @param joueurBleu
	 *            Joueur bleu.
	 */
	public Jeu(final INeutronUI i_NeutronUI, final IJoueur joueurRouge, final IJoueur joueurBleu)
	{
		Constructeur(i_NeutronUI, joueurRouge, joueurBleu, System.out);
	}

	/**
	 * Cr�� un jeu avec les joueurs donn�s.
	 * 
	 * @param i_NeutronUI
	 *            Interface Utilisateur.
	 * @param joueurRouge
	 *            Joueur rouge.
	 * @param joueurBleu
	 *            Joueur bleu.
	 * @param sortie
	 *            Flux de sortie.
	 */
	public Jeu(final INeutronUI i_NeutronUI, final IJoueur joueurRouge, final IJoueur joueurBleu, final PrintStream sortie)
	{
		Constructeur(i_NeutronUI, joueurRouge, joueurBleu, sortie);
	}

	/**
	 * Constructeur r�el (tous les param�tres)
	 * 
	 * @see #Jeu(IJoueur, IJoueur, PrintStream)
	 */
	private void Constructeur(final INeutronUI i_NeutronUI, final IJoueur joueurRouge, final IJoueur joueurBleu, final PrintStream sortie)
	{
		m_joueurBleu = joueurBleu;
		m_joueurRouge = joueurRouge;
		m_Historique.clear();
		m_CurseurHistorique = 0;
		m_sortie = sortie;
		m_UI = i_NeutronUI;
	}

	/**
	 * Red�marre une nouvelle partie.
	 */
	public void Redemarrer()
	{
		Stopper();

		m_Historique.clear();
		m_CurseurHistorique = 0;
		m_plateau = new Plateau();
		m_Vainqueur = eCase.VIDE;
		Demarrer();
	}

	public void Stopper()
	{
		if ((m_Thread != null) && (m_Thread.isAlive()))
		{
			m_Thread.interrupt();
			try
			{
				m_Thread.join();
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}

	/**
	 * D�marre la partie.
	 * 
	 * @param sortie
	 *            : flux de sortie
	 */
	public synchronized void Demarrer()
	{
		// On teste si l'on est d�j� d�marr�
		if (m_Thread != null)
		{
			if (m_Thread.isAlive())
			{
				// On est d�j� connect�.
				// Le jeu est d�j� entamm�
				if (m_Historique.size() > 0)
				{
					// Assert
					throw new RuntimeException(ERREUR_JEU_DEMARRE);
				}
				return;
			}
		}

		m_Thread = new Thread("Neutron.Jeu")
		{
			/**
			 * Process principal du thread client.
			 */
			public void run()
			{
				try
				{
					m_Vainqueur = eCase.VIDE;
					Coup coup_courant = null;
					IJoueur joueur_courant = null;

					m_UI.setPlateau(m_plateau);
					
					ajouterHistoriquePlateau(m_plateau);

					// On joue tant que le plateau n'est pas terminal.
					// while(m_plateau.testerPlateauTerminal(true) == eCase.VIDE)
					while ((m_Vainqueur = m_plateau.testerPlateauTerminal(true)) == eCase.VIDE)
					{
						// On regarde � qui est le tour
						if (m_plateau.getTour() == eCase.BLEUE)
						{
							joueur_courant = m_joueurBleu;
						}
						else
						// if (m_plateau.getTour() == eCase.ROUGE)
						{
							joueur_courant = m_joueurRouge;
						}

						// On demande au joueur courant de jouer, tant qu'il ne propose pas
						// de coup valable
						do
						{
							coup_courant = joueur_courant.Jouer(m_plateau);
						} while ((coup_courant != null) && (m_plateau.testerValiditeCoup(coup_courant) == false));

						// Un coup null signifie que le joueur ne peut pas jouer sur ce plateau.
						if (coup_courant == null)
						{
							m_sortie.println(String.format("%s abandonne la partie.", m_plateau.getTour()));
						}
						else
						{
							m_sortie.println(String.format("%s joue %s", m_plateau.getTour(), coup_courant));
							m_plateau = m_plateau.jouerCoup(coup_courant);
							m_UI.setPlateau(m_plateau);
						}

						ajouterHistoriquePlateau(m_plateau);
					
						// Pass� 30 plateaux, on consid�re la partie comme nulle.
						if (getNbPlateaux() >= 30)
						{
							m_Vainqueur = eCase.NEUTRON;
							// return eCase.NEUTRON;
						}
					}

					m_UI.setPlateau(m_plateau);
					m_UI.OnJeuFini(m_Vainqueur);

					// return m_Vainqueur;
					m_joueurBleu.OnJeuFini(m_Vainqueur);
					m_joueurRouge.OnJeuFini(m_Vainqueur);
				}
				catch (InterruptedException e)
				{
					m_joueurBleu.Stopper();
					m_joueurRouge.Stopper();
					System.out.println("Partie interrompue.");
				}
			}
		};

		m_Thread.start();
	}

	/**
	 * Change de joueur bleu.
	 * 
	 * @param bleu
	 *            Nouveau joueur bleu.
	 */
	public void setJoueurBleu(IJoueur bleu)
	{
		m_joueurBleu = bleu;
	}

	/**
	 * Change de joueur rouge.
	 * 
	 * @param rouge
	 *            Nouveau joueur rouge.
	 */
	public void setJoueurRouge(IJoueur rouge)
	{
		m_joueurRouge = rouge;
	}
	
	private void ajouterHistoriquePlateau(Plateau plateau)
	{
		if ((m_Historique.size() > 0) && (m_Historique.get(m_CurseurHistorique-1).equals(plateau))) return;
		
		if (m_Historique.size() > m_CurseurHistorique)
		{
			m_Historique = m_Historique.subList(0, m_CurseurHistorique);
		}
		
		m_Historique.add(plateau.clone());
		m_CurseurHistorique = m_Historique.size();
	}
	
	private Plateau getHistoriquePlateau(int curseur)
	{
		return m_Historique.get(curseur).clone();
	}

	/**
	 * Annule le coup pr�c�dent (remonte d'un plateau dans l'historique).
	 */
	public void Precedent()
	{
		int pas = 2;
		if (m_Historique.size() <= pas) return;
		
		Stopper();

		m_CurseurHistorique = m_CurseurHistorique-pas;
		m_plateau = getHistoriquePlateau(m_CurseurHistorique-1);
		
		Demarrer();
	}

	/**
	 * Parcours l'historique des plateau vers l'avant. Suppose que l'on ai fait appel � "Precedent".
	 */
	public void Suivant()
	{
		int pas = 2;
		
		if ((m_CurseurHistorique + pas) > m_Historique.size()) return;
		
		Stopper();
		
		m_CurseurHistorique = m_CurseurHistorique + pas;
		m_plateau = getHistoriquePlateau(m_CurseurHistorique-1);
		
		Demarrer();
	}

	/**
	 * Retourne l'historique de la partie jusqu'au coup courant.
	 */
	public List<Plateau> getHistorique()
	{
		return m_Historique;
	}

	/**
	 * Retourne le nombre de plateau de la partie.
	 */
	public int getNbPlateaux()
	{
		return m_Historique.size();
	}

	/**
	 * Sauvegarde l'historique courant dans un fichier texte. Si pointDeVue est diff�rent de Vide ou Neutron, tous les plateau sont convertis au point de vue donn�.
	 * 
	 * @param nomFichier
	 *            : nom de fichier sous lequel sauvegarder.
	 * @param pointDeVue
	 *            : point de vue de la partie auquel forcer tous les plateau.
	 * @throws FileNotFoundException
	 *             : Si le fichier ne peut �tre cr��.
	 */
	public void sauverHistorique(final String nomFichier, final eCase pointDeVue) throws FileNotFoundException, DrawGameException
	{
		// S'il n'y a pas de vainqueur et que l'on veut un point de vue particuli�, on jette une exception.
		if ((m_Vainqueur != eCase.ROUGE) && (m_Vainqueur != eCase.BLEUE))
		{
			throw new DrawGameException("Impossible de donner un point de vue � une partie nulle");
		}
		PrintStream ps = new PrintStream(nomFichier);
		float poids = 0, taille = m_Historique.size();
		Plateau plateau = null;
		StringBuffer sb = new StringBuffer(30);

		for (int i = 0; i < taille; ++i)
		{
			plateau = m_Historique.get(i);

			poids = ((float) i / (float) (taille * 2));

			if (plateau.getTour() == m_Vainqueur)
			{
				sb.replace(0, 30, String.format("+%03f", poids));
			}
			else
			{
				sb.replace(0, 30, String.format("-%03f", poids));
			}

			if ((pointDeVue != eCase.NEUTRON) && (pointDeVue != eCase.VIDE) && (plateau.getTour() != pointDeVue))
			{
				plateau = plateau.inverserPlateau();
			}
			ps.print(plateau.toString() + '\t');
			ps.println(sb.toString());
		}

	}

	/**
	 * @return the vainqueur
	 */
	public eCase getVainqueur()
	{
		return m_Vainqueur;
	}

	public boolean estTermine()
	{
		return (m_Vainqueur != eCase.VIDE);
	}

}
